Skip to content

bugfix(skirmish): Set consistent seed values for skirmish games to prevent mismatches for restarted skirmish games#2270

Open
Caball009 wants to merge 3 commits intoTheSuperHackers:mainfrom
Caball009:fix_bug_skirmish_restart_seed_values
Open

bugfix(skirmish): Set consistent seed values for skirmish games to prevent mismatches for restarted skirmish games#2270
Caball009 wants to merge 3 commits intoTheSuperHackers:mainfrom
Caball009:fix_bug_skirmish_restart_seed_values

Conversation

@Caball009
Copy link

@Caball009 Caball009 commented Feb 8, 2026

This PR makes the seed value consistent for started vs. restarted skirmish games, and fixes a bug that causes replays of restarted skirmish game to always mismatch.

TODO:

  • Replicate in Generals.

@Caball009 Caball009 added Bug Something is not working right, typically is user facing Major Severity: Minor < Major < Critical < Blocker Stability Concerns stability of the runtime labels Feb 8, 2026
@greptile-apps
Copy link

greptile-apps bot commented Feb 8, 2026

Greptile Summary

This PR fixes a seed mismatch bug for restarted skirmish games by ensuring the same random seed used to start a skirmish game is also used when the game is restarted via the Quit menu. Changes are mirrored across both Generals/ and GeneralsMD/ (Zero Hour).

Key changes:

  • QuitMenu.cpp (both variants): Captures the skirmish seed from TheSkirmishGameInfo->getSeed() before clearGameData(FALSE) is called, guarded by a DEBUG_ASSERTCRASH that validates the TheSkirmishGameInfo/gameMode invariant. The captured seed is then passed to InitRandom(seed) instead of the hardcoded 0 that caused the mismatch.
  • SkirmishGameOptionsMenu.cpp (both variants): Moves InitGameLogicRandom inside the isSkirmish/else branches so the true skirmish seed is used for multiplayer maps and 0 for single-player maps started from the skirmish menu — making initial start and restart consistent.

Minor issue found:

  • GameInfo::getSeed() returns Int (signed), but seed is declared as const UnsignedInt in both QuitMenu.cpp files, creating an implicit signed-to-unsigned conversion. While harmless in practice given the existing sign conventions in the codebase, aligning the type to Int would be more precise.

Confidence Score: 4/5

  • This PR is safe to merge; the bug fix is correct and well-guarded, with only a minor type mismatch style issue.
  • The core logic is sound: the seed is captured before clearGameData, TheSkirmishGameInfo is confirmed to survive clearGameData(FALSE), and the assert correctly documents the invariant. The SkirmishGameOptionsMenu changes cleanly separate the seed initialization per game type. One minor style concern (Int vs UnsignedInt for seed) does not affect correctness.
  • No files require special attention beyond the minor type alignment note in both QuitMenu.cpp variants.

Important Files Changed

Filename Overview
Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp Adds seed capture before clearGameData and reuses it for skirmish restarts. Logic is sound — TheSkirmishGameInfo survives clearGameData(FALSE) and the seed is correctly captured early. One pre-existing signed/unsigned mismatch in type (Int getSeed → UnsignedInt seed) is harmless in practice.
GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp Mirror of the Generals/ changes. Seed is captured before clearGameData, assert guards the TheSkirmishGameInfo/gameMode invariant, and InitRandom(seed) replaces the hardcoded 0. Same signed/unsigned type mismatch (Int getSeed → UnsignedInt seed) as Generals variant, harmless in practice.
Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/SkirmishGameOptionsMenu.cpp Moves InitGameLogicRandom call to inside the isSkirmish/else branches so it is correctly called with the actual seed for skirmish maps and with 0 for single-player maps. Clean and correct change.
GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/SkirmishGameOptionsMenu.cpp Mirror of Generals/ changes. InitGameLogicRandom correctly moved inside the isSkirmish branch (with actual seed) and else branch (with 0). No issues found.

Sequence Diagram

sequenceDiagram
    participant User
    participant QuitMenu
    participant TheSkirmishGameInfo
    participant TheGameLogic
    participant InitRandom

    Note over User,InitRandom: Initial skirmish game start (SkirmishGameOptionsMenu)
    User->>TheSkirmishGameInfo: setSeed(GetTickCount())
    User->>TheSkirmishGameInfo: startGame(0)
    alt isSkirmish == TRUE
        TheSkirmishGameInfo-->>QuitMenu: getSeed()
        QuitMenu->>InitRandom: InitGameLogicRandom(seed)
    else isSkirmish == FALSE (solo map)
        QuitMenu->>InitRandom: InitGameLogicRandom(0)
    end

    Note over User,InitRandom: Restarting skirmish game (QuitMenu - restartMissionMenu)
    User->>QuitMenu: Click Restart
    QuitMenu->>TheGameLogic: getGameMode()
    TheGameLogic-->>QuitMenu: gameMode (e.g. GAME_SKIRMISH)
    QuitMenu->>TheSkirmishGameInfo: getSeed() [captured BEFORE clearGameData]
    TheSkirmishGameInfo-->>QuitMenu: seed
    QuitMenu->>TheGameLogic: clearGameData(FALSE)
    Note over QuitMenu: TheSkirmishGameInfo survives clearGameData
    alt replayFile is empty (new game)
        QuitMenu->>InitRandom: InitRandom(seed) [skirmish: reuses original seed]
    else replayFile not empty (replay)
        QuitMenu->>QuitMenu: TheRecorder->playbackFile(replayFile)
    end
Loading

Last reviewed commit: 00bddf4

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

@Caball009
Copy link
Author

Caball009 commented Feb 8, 2026

I did find a single case where the game would start a new GAME_SINGLE_PLAYER game bit without using a non-zero seed value. I've added that to this PR for more consistent seed values.

@Caball009 Caball009 force-pushed the fix_bug_skirmish_restart_seed_values branch from 6dacb8a to 45fa3ea Compare February 9, 2026 01:49
@Caball009 Caball009 changed the title bugfix(skirmish): Avoid mismatches for restarted skirmish games bugfix(skirmish): Set consistent seed value for skirmish games to prevent mismatches for restarted skirmish games Feb 9, 2026
@Caball009
Copy link
Author

Caball009 commented Feb 9, 2026

EDIT: Maybe I didn't properly explain what I documented here. The findings below explain how the seed value is set for different game modes. I noted the execution flow with the current changes of this PR. TheGameInfo ... && TheSkirmishGameInfo ... indicate what the values of these globals are inside the restartMissionMenu function.

Here are the results of my testing with this PR:

1. Campaign (GAME_SINGLE_PLAYER):
Start: doGameStart -> InitRandom( 0 )
Restart: restartMissionMenu -> InitRandom( (gameMode == GAME_SKIRMISH) ? TheSkirmishGameInfo->getSeed() : 0 )
TheGameInfo == nullptr && TheSkirmishGameInfo == nullptr

2. Challenges (GAME_SINGLE_PLAYER):
Start: ChallengeMenuSystem -> InitRandom( 0 )
Restart: restartMissionMenu -> InitRandom( (gameMode == GAME_SKIRMISH) ? TheSkirmishGameInfo->getSeed() : 0 )
TheGameInfo != nullptr && TheSkirmishGameInfo == nullptr

3. Skirmish, single player map / scenario (GAME_SINGLE_PLAYER):
Start: reallyDoStart -> InitGameLogicRandom( 0 )
Restart: restartMissionMenu -> InitRandom( (gameMode == GAME_SKIRMISH) ? TheSkirmishGameInfo->getSeed() : 0 )
TheGameInfo == nullptr && TheSkirmishGameInfo == nullptr

4. Skirmish, multiplayer map (GAME_SKIRMISH):
Start: reallyDoStart -> InitGameLogicRandom( TheSkirmishGameInfo->getSeed() )
Restart: restartMissionMenu -> InitRandom( (gameMode == GAME_SKIRMISH) ? TheSkirmishGameInfo->getSeed() : 0 )
TheGameInfo != nullptr && TheSkirmishGameInfo != nullptr

@Caball009
Copy link
Author

Caball009 commented Feb 9, 2026

If we ever to enable replays for campaign / challenge / single player scenarios, we may need to verify that this issue doesn't happen there as well. I don't see anything that could still present an issue in that regard, though.

@Caball009 Caball009 changed the title bugfix(skirmish): Set consistent seed value for skirmish games to prevent mismatches for restarted skirmish games bugfix(skirmish): Set consistent seed values for skirmish games to prevent mismatches for restarted skirmish games Feb 9, 2026
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@Caball009 Caball009 force-pushed the fix_bug_skirmish_restart_seed_values branch from 3208aa9 to 4b6d3dc Compare February 9, 2026 19:34
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Copy link

@xezon xezon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this is correct then.

@Caball009
Copy link
Author

Caball009 commented Feb 15, 2026

I expect to update this PR in a couple of days.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@Caball009 Caball009 force-pushed the fix_bug_skirmish_restart_seed_values branch from 5a53659 to 096d621 Compare February 17, 2026 20:59
@Caball009
Copy link
Author

Replicated in Generals, and ready to be merged, I think.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +203 to +206
// TheSuperHackers @bugfix Caball009 07/02/2026 Reuse the previous seed value for the new skirmish match to prevent mismatches.
// Campaign, challenge, and skirmish single-player scenarios all use GAME_SINGLE_PLAYER and are expected to use 0 as seed value.
DEBUG_ASSERTCRASH((TheSkirmishGameInfo != nullptr) == (gameMode == GAME_SKIRMISH), ("Unexpected game mode on map / mission restart"));
const UnsignedInt seed = TheSkirmishGameInfo ? TheSkirmishGameInfo->getSeed() : 0;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signed-to-unsigned implicit conversion on seed

Same as in Generals/: GameInfo::getSeed() returns Int (signed), but seed is declared as const UnsignedInt. This creates an implicit signed-to-unsigned conversion. Consider declaring seed as Int to match the return type of getSeed().

Suggested change
// TheSuperHackers @bugfix Caball009 07/02/2026 Reuse the previous seed value for the new skirmish match to prevent mismatches.
// Campaign, challenge, and skirmish single-player scenarios all use GAME_SINGLE_PLAYER and are expected to use 0 as seed value.
DEBUG_ASSERTCRASH((TheSkirmishGameInfo != nullptr) == (gameMode == GAME_SKIRMISH), ("Unexpected game mode on map / mission restart"));
const UnsignedInt seed = TheSkirmishGameInfo ? TheSkirmishGameInfo->getSeed() : 0;
const Int seed = TheSkirmishGameInfo ? TheSkirmishGameInfo->getSeed() : 0;

Context Used: Rule from dashboard - Use nullptr instead of NULL for null pointer literals in C++ code (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp
Line: 203:206

Comment:
**Signed-to-unsigned implicit conversion on seed**

Same as in `Generals/`: `GameInfo::getSeed()` returns `Int` (signed), but `seed` is declared as `const UnsignedInt`. This creates an implicit signed-to-unsigned conversion. Consider declaring `seed` as `Int` to match the return type of `getSeed()`.

```suggestion
	const Int seed = TheSkirmishGameInfo ? TheSkirmishGameInfo->getSeed() : 0;
```

**Context Used:** Rule from `dashboard` - Use nullptr instead of NULL for null pointer literals in C++ code ([source](https://app.greptile.com/review/custom-context?memory=c69cf1a9-c69a-4330-a013-69bb5d6701da))

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could do static_cast<UnsignedInt>(TheSkirmishGameInfo ? TheSkirmishGameInfo->getSeed() : 0) to make the conversion explicit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something is not working right, typically is user facing Major Severity: Minor < Major < Critical < Blocker Stability Concerns stability of the runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replay will mismatch 100% when Skirmish Game is recorded after RESTART GAME

2 participants